
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>基于K8S构建企业级Jenkins CICD平台实战（二） 之 kubernetes-plugin 插件使用</title>
            </head>
            <body>
            <a href="https://andyoung.blog.csdn.net">原作者博客</a>
            <div id="content_views" class="markdown_views prism-atom-one-light">
                    <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                        <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                    </svg>
                    <h3><a id="1_K8S__3"></a>1. 传统架构与K8S 架构的区别</h3> 
<p>(1). 传统Master/Slave架构，Master收到Job后，将请求转发到Slave节点处理。Slave节点数固定，Slave节点未能自动申缩容。</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/76935b959351bbdfeea4bb2082295640.png" alt="Jenkins Master/Slave架构"></p> 
<p>(2). K8S中Jenkins Master/Slave架构，Master收到Job后，会自动创建Slave节点处理此Job，根据客户端的Job自动申缩容。</p> 
<p>Jenkins的<code>kubernetes-plugin</code>在执行构建时会在kubernetes集群中自动创建一个Pod，并在Pod内部创建一个名为jnlp的容器，该容器会连接Jenkins并运行Agent程序，形成一个Jenkins的Master和Slave架构，然后Slave会执行构建脚本进行构建。如果要在集群集群内部进行部署操作可以使用kubectl执行命令，要解决kubectl的安装和权限分配问题。</p> 
<p>因为默认的jnlp容器可以执行的命令比较少，所以要实现执行kubectl命令，就要自定义构建Docker Image，因为一个Pod内部可以运行多个容器，所以可以用自定义的Docker容器实现上述目的（下放实现）。</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/c22da29fe9193c2dd41d128ee5fe0d0b.png" alt="K8S中Jenkins Master/Slave架构"></p> 
<h3><a id="2_jenkinsK8S_25"></a>2. jenkins插件连接K8S配置</h3> 
<p>需要先安装pipeline插件, 建议不在 pipeline UI上配置pod创建模版，免得以后每来一个项目都要创建，管理不方便，建议使用pipeline统一 配置。<br> Kubernetes插件介绍:<a href="https://github.com/jenkinsci/kubernetes-plugin">https://github.com/jenkinsci/kubernetes-plugin</a><br> 配置路径 “系统管理” —&gt; “系统设置” —&gt; “云（翻到最后）” —&gt; “Kubernetes”<br> 新版已放到 “节点管理” -&gt; “configureClouds” (&gt; v2.343)<br> <img src="https://i-blog.csdnimg.cn/blog_migrate/399a7689db16b021009507ce6e65b615.jpeg" alt="在这里插入图片描述"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/0d7bc3f71be50b3473f4e491359751cb.png" alt="img"></p> 
<blockquote> 
 <ol><li>Name 处默认为 kubernetes，也可以修改为其他名称，如果这里修改了，下边在执行 Job 时指定 podTemplate() 参数 cloud 为其对应名称，否则会找不到，cloud 默认值取：kubernetes</li><li>Kubernetes URL 处我填写了 https://kubernetes.default 这里我填写了 Kubernetes Service 对应的 DNS 记录，通过该 DNS 记录可以解析成该 Service 的 Cluster IP，注意：也可以填写 https://kubernetes.default.svc.cluster.local 完整 DNS 记录，因为它要符合 <code>&lt;svc_name&gt;.&lt;namespace_name&gt;.svc.cluster.local</code> 的命名方式，或者直接填写外部 Kubernetes 的地址 <code>https://&lt;ClusterIP&gt;:&lt;Ports&gt;</code>。</li><li>Jenkins URL 处我填写了 http://jenkins.default，跟上边类似，也是使用 Jenkins Service 对应的 DNS 记录，不过要指定为 8080 端口，因为我们设置暴漏 8080 端口。同时也可以用 <code>http://&lt;ClusterIP&gt;</code>:&lt;Node_Port&gt; 方式，例如我这里可以填 http://192.168.99.100:30645 也是没有问题的，这里的 30645 就是对外暴漏的 NodePort。<br> 注意 命名空间</li></ol> 
</blockquote> 
<h4><a id="3_43"></a>3.测试并验证</h4> 
<p>通过 Kubernetes 安装 Jenkins Master 完毕并且已经配置好了连接，接下来，我们可以配置 Job 测试一下是否会根据配置的 Label 动态创建一个运行在 Docker Container 中的 Jenkins Slave 并注册到 Master 上，而且运行完 Job 后，Slave 会被注销并且 Docker Container 也会自动删除吧！</p> 
<h4><a id="4pipeline__47"></a>4.pipeline 类型支持</h4> 
<blockquote> 
 <p>创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-pipeline，然后在 Pipeline 脚本处填写一个简单的测试脚本如下：</p> 
</blockquote> 
<pre><code>def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes')
{
    node(label) {
        stage('Run shell') {
            sh 'sleep 30s'
            sh 'echo hello world.'
        }
    }
}
</code></pre> 
<blockquote> 
 <p>node(label ) 中的 <code>label</code> ：匹配 node 配置 为 <code>label</code>的节点。</p> 
 <p>podTemplate(label） 中的 <code>label</code>: 定义 节点 node 运行 podTemplate 的 label ，也就是 节点node的 label</p> 
 <p>可以看出 <code>podTemplate</code> 和<code>node</code> 配置并没有相关，只需要label 相关联</p> 
</blockquote> 
<p>执行构建，此时去构建队列里面，可以看到有一个构建任务，暂时还没有执行中的构建，因为还没有初始化好，稍等一会，就会看到 Master 和 <code>jenkins-slave-jbs4z-xs2r8</code> 已经创建完毕，在等一会，就会发现 <code>jenkins-slave-jbs4z-xs2r8</code> 已经注册到 Master 中，并开始执行 Job，点击该 Slave 节点，我们可以看到通过标签 <code>mypod-b538c04c-7c19-4b98-88f6-9e5bca6fc9ba</code> 关联，该 <code>Label</code> 就是我们定义的标签格式生成的，Job 执行完毕后，jenkins-slave 会自动注销，我们通过 kubectl 命令行，可以看到整个自动创建和删除过程。</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/8bfac4665f8635a8890ead08b58c6606.png" alt="image-20211209173812924"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/d0eb75cb5258abf5856d4678356f1024.png" alt="image-20211209173823909"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/b65f38eea38400a06be9d966e91b2bb8.png" alt="image-20211209173839496"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/d974b2455f8eca9353ac2607027050b6.png" alt="image-20211209173902319"></p> 
<h4><a id="5_Container_Group__82"></a>5. Container Group 类型支持</h4> 
<p>创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-container，然后在 Pipeline 脚本处填写一个简单的测试脚本如下：</p> 
<pre><code>def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes', containers: [
    containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
  ]) {

    node(label) {
        stage('Get a Maven Project') {
            git 'https://github.com/jenkinsci/kubernetes-plugin.git'
            container('maven') {
                stage('Build a Maven project') {
                    sh 'mvn -B clean install'
                }
            }
        }
    }
}
</code></pre> 
<p>注意：这里我们使用的 containers 定义了一个 containerTemplate 模板，指定名称为 maven 和使用的 Image，下边在执行 Stage 时，使用 <code>container('maven'){...}</code> 就可以指定在该容器模板里边执行相关操作了。比如，该示例会在 jenkins-slave 中执行 <code>git clone</code> 操作，然后进入到 maven 容器内执行 <code>mvn -B clean install</code> 编译操作。这种操作的好处就是，我们只需要根据代码类型分别制作好对应的编译环境镜像，通过指定不同的 container 来分别完成对应代码类型的编译操作。模板详细的各个参数配置可以参照 <code>Pod and container template configuration</code>。</p> 
<p>执行构建，跟上边 Pipeline 类似，也会新建 jenkins-slave 并注册到 master，不同的是，它会在 Kubernetes 中启动我们配置的 maven 容器模板，来执行相关命令。</p> 
<h5><a id="Console_Output_110"></a>Console Output</h5> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/c5d04aaaad919a25e63be4a0143a5463.png" alt="image-20211209174602015"></p> 
<h5><a id="_yaml__114"></a>生成的 yaml 文件</h5> 
<pre><code>apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.ops.svc.cluster.local/job/maven/7/"
    runUrl: "job/maven/7/"
  labels:
    jenkins: "slave"
    jenkins/label-digest: "42a877c388ee17d414289d422ca60f5fd36ac08f"
    jenkins/label: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f"
  name: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f-h8pqj-bnj15"
spec:
  containers:
  - command:
    - "cat"
    image: "maven:3.3.9-jdk-8-alpine"
    imagePullPolicy: "IfNotPresent"
    name: "maven"
    resources:
      limits: {}
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f-h8pqj-bnj15"
    - name: "JENKINS_NAME"
      value: "mypod-75997681-42e1-4ed6-bd02-8ac5df191d4f-h8pqj-bnj15"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.ops.svc.cluster.local/"
    image: "jenkins/inbound-agent:4.11-1-jdk11"
    name: "jnlp"
    resources:
      limits: {}
      requests:
        memory: "256Mi"
        cpu: "100m"
    volumeMounts:
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - emptyDir:
      medium: ""
    name: "workspace-volume"
</code></pre> 
<p>可以看到一个pod下面有两个 docker启动了。一个是默认的<code>jnlp</code>（jenkins slave agent）另外一个是我们配置的 <code>maven</code>。jnlp 负责与jenkins master 通讯，<code>maven</code> 负责打包。</p> 
<h4><a id="6_Pipeline__178"></a>6.非 Pipeline 类型支持</h4> 
<p>jenkins 中除了使用 Pipeline 方式运行 Job 外，通常我们也会使用普通类型 Job，如果也要想使用kubernetes plugin 来构建任务，那么就需要点击 “系统管理” —&gt; “系统设置” —&gt; “云” —&gt; “Kubernetes” —&gt; “Add Pod Template” 进行配置 “Kubernetes Pod Template” 信息。</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/ec336cf88378af59bf86cd9292144f08.png" alt="image-20211209175013918"></p> 
<p>注意：这里的 Labels 名在配置非 pipeline 类型 Job 时，用来指定任务运行的节点。Containers 下的 Name 字段的名字，这里要注意的是，<strong>如果 Name 配置为 jnlp，那么 Kubernetes 会用下边指定的 Docker Image 代替默认的 jenkinsci/jnlp-slave 镜像，否则，Kubernetes plugin 还是会用默认的 jenkinsci/jnlp-slave 镜像与 Jenkins Server 建立连接</strong>，即使我们指定其他 Docker Image。这里我随便配置为 jnlp-slave，意思就是使用默认的 jenkinsci/jnlp-slave 镜像来运行，因为我们暂时还没制作可以替代默认镜像的镜像。</p> 
<p>新建一个自由风格的 Job 名称为 my-k8s-jenkins-simple，配置 “Restrict where this project can be run” 勾选，在 “Label Expression” 后边输出我们上边创建模板是指定的 Labels 名称 jnlp-agent，意思是指定该 Job 匹配 jnlp-agent 标签的 Slave 上运行。</p> 
<h4><a id="7__jenkinsslave__190"></a>7. 配置自定义 jenkins-slave 镜像</h4> 
<p>通过 <code>kubernetest plugin</code> 默认提供的镜像 jenkinsci/jnlp-slave 可以完成一些基本的操作，它是基于 <code>openjdk:8-jdk</code> 镜像来扩展的，但是对于我们来说这个镜像功能过于简单，比如我们想执行 Maven 编译或者其他命令时，就有问题了，那么可以通过制作自己的镜像来预安装一些软件，既能实现 jenkins-slave 功能，又可以完成自己个性化需求，那就比较不错了。如果我们从头开始制作镜像的话，会稍微麻烦些，不过可以参考 jenkinsci/jnlp-slave 和 jenkinsci/docker-slave 这两个官方镜像来做，注意：jenkinsci/jnlp-slave 镜像是基于 jenkinsci/docker-slave 来做的。这里我简单演示下，基于 jenkinsci/jnlp-slave:latest 镜像，在其基础上做扩展，安装 Maven 到镜像内，然后运行验证是否可行吧。</p> 
<p>创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-container-custom，然后在 Pipeline 脚本处填写一个简单的测试脚本如下：</p> 
<pre><code>def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes',containers: [
    containerTemplate(
        name: 'jnlp', 
        image: 'andanyoung/jenkins-slave-maven:latest', 
        alwaysPullImage: false, 
        args: '${computer.jnlpmac} ${computer.name}'),
  ]) {

    node(label) {
        stage('stage1') {
            stage('Show Maven version') {
                sh 'mvn -version'
                sh 'sleep 60s'
            }
        }
    }
}
</code></pre> 
<p>说明一下：这里 containerTemplate 的<font color="red"><strong>name 属性必须叫 jnlp</strong></font> ，Kubernetes 才能用自定义 images 指定的镜像替换默认的 jenkinsci/jnlp-slave 镜像。此外，args 参数传递两个 jenkins-slave 运行需要的参数。还有一点就是这里并不需要指定 <code>container('jnlp'){...}</code> 了，因为它被 Kubernetes 指定了要被执行的容器，所以直接执行 Stage 就可以了。</p> 
<p>最后，贴一下我自定义的预安装了 Maven 的 Jenkins-slave 镜像的 Dockerfile，当然大家可以基于此预安装一些其他软件，来完成日常持续构建与发布工作吧。</p> 
<p>当然也可以直接使用我的 <code>andanyoung/jenkins-slave-maven</code> 镜像</p> 
<pre><code># Dockerfile
FROM jenkins/jnlp-slave:latest

LABEL Author  andanyang

LABEL Description "This is a extend image base from jenkins/jnlp-slave which install maven in it."

# 切换到 root 账户进行操作
USER root
VOLUME [ "/home/jenkins/.m2" ]

ARG MAVEN_VERSION=3.8.2
# 安装 maven https://archive.apache.org/dist/maven/maven-3/3.8.2/binaries/apache-maven-3.8.2-bin.tar.gz
RUN curl -OL https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz &amp;&amp; \
  tar -zxf ./apache-maven-${MAVEN_VERSION}-bin.tar.gz &amp;&amp; \
  mv apache-maven-${MAVEN_VERSION} /usr/local &amp;&amp; \
  rm -f apache-maven-${MAVEN_VERSION}-bin.tar.gz &amp;&amp; \
  ln -s /usr/local/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/bin/mvn &amp;&amp; \
  ln -s /usr/local/apache-maven-${MAVEN_VERSION} /usr/local/apache-maven

USER jenkins

## docker build --build-arg MAVEN_VERSION=3.8.2  -t andanyoung/jenkins-slave-maven:3.8.2 .
## docker push andanyoung/jenkins-slave-maven:3.8.2
</code></pre> 
<h3><a id="3__Pod_Templates_252"></a>3. 使用 Pod Templates</h3> 
<p>为了方便配置一个Pod Templates，在配置kubernetes连接内容的下面，这里的模板只是模板（与类一样使用时还要实例化过程）,名称和标签列表不要以为是Pod的name和label，这里的名称和标签列表只是Jenkins查找选择模板时使用的，Jenkins自动创建Pod的name是项目名称+随机字母的组合，所以我们填写jnlp-maven，命名空间填写ops（创建命令：kubectl create namespace ops），Pod内添加一个容器名称是 jnlp （替换掉默认的自动创建jnlp容器），Docker镜像填写：andanyoung/jenkins-slave-maven，也可以是私服地址（<a href="https://andyoung.blog.csdn.net/article/details/115691894" rel="nofollow">搭建Docker私有仓库</a>），下面增加<code>m2</code>maven仓库的持久化 PVC 配置 用户保存 maven 的 setting.xml 配置文件和 本地仓库，保存回到系统管理页面。</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/da16129e4412fb94f145046b2681d7a8.png" alt="image-20211210143840002"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/67df16ad1b0ac3214770636333df7a1b.png" alt="image-20211210143943888"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/1ddbd8b8c6571274168dc3b323a1938d.png" alt="image-20211210145350369"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/03f39d7cebb1ef74a77570457f2ea1dc.png" alt="image-20211210144037858"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/8d8dc242d75f339fba3d35d6bb4317b8.png" alt="image-20211210151144580"></p> 
<blockquote> 
 <p>如果使用的是私服需要设置 Image pull Secret(生成 Image pull Secret <a href="https://andyoung.blog.csdn.net/article/details/120313644" rel="nofollow">https://andyoung.blog.csdn.net/article/details/120313644</a> )</p> 
</blockquote> 
<ul><li>setting.xml 文件示例（将其放入<code>pvc-maven-m2</code>中）</li></ul> 
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;

&lt;settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"&gt;
  &lt;pluginGroups&gt;
  &lt;/pluginGroups&gt;

  &lt;proxies&gt;
  &lt;/proxies&gt;

  &lt;servers&gt;
  &lt;/servers&gt;

  &lt;mirrors&gt;
    &lt;mirror&gt;
      &lt;id&gt;central&lt;/id&gt;
      &lt;mirrorOf&gt;central&lt;/mirrorOf&gt;
      &lt;name&gt;aliyun maven&lt;/name&gt;
      &lt;url&gt;https://maven.aliyun.com/repository/public&lt;/url&gt;
    &lt;/mirror&gt;
  &lt;/mirrors&gt;

  &lt;profiles&gt;
  &lt;/profiles&gt;

&lt;/settings&gt;
</code></pre> 
<h4><a id="_300"></a>测试运行</h4> 
<pre><code>podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
        container('jnlp'){
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
            }
        }
    }
}
</code></pre> 
<ul><li>podTemplate：用Pod模版实例化一个Pod配置并在kubernetes内自动创建</li><li>inheritFrom：意思是创建的Pod配置继承自jnlp-maven模版</li><li>POD_LABEL：自动创建Pod的label</li><li>container：选择哪个容器执行脚本(默认就是 jnlp，<code> container('jnlp'){..}</code> 最外层可以不写)</li></ul> 
<p>执行构建结果：</p> 
<pre><code>[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Created Pod: kubernetes ops/maven-13-pbvhr-xbdq3-wb3j8
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Scheduled] Successfully assigned ops/maven-13-pbvhr-xbdq3-wb3j8 to a3
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Pulled] Container image "andanyoung/jenkins-slave-maven" already present on machine
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Created] Created container jnlp
[Normal][ops/maven-13-pbvhr-xbdq3-wb3j8][Started] Started container jnlp
Agent maven-13-pbvhr-xbdq3-wb3j8 is provisioned from template maven_13-pbvhr-xbdq3
---
apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.ops.svc.cluster.local/job/maven/13/"
    runUrl: "job/maven/13/"
  labels:
    jenkins: "slave"
    jenkins/label-digest: "a3166022e7be1cbf48366e0c0ffece53242411af"
    jenkins/label: "maven_13-pbvhr"
  name: "maven-13-pbvhr-xbdq3-wb3j8"
spec:
  containers:
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "maven-13-pbvhr-xbdq3-wb3j8"
    - name: "JENKINS_NAME"
      value: "maven-13-pbvhr-xbdq3-wb3j8"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.ops.svc.cluster.local/"
    image: "andanyoung/jenkins-slave-maven"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp"
    resources:
      limits:
        memory: "1Gi"
        cpu: "1"
      requests: {}
    tty: false
    volumeMounts:
    - mountPath: "/home/jenkins/.m2"
      name: "volume-0"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  hostNetwork: false
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - name: "volume-0"
    persistentVolumeClaim:
      claimName: "pvc-maven-m2"
      readOnly: false
  - emptyDir:
      medium: ""
    name: "workspace-volume"

Running on maven-13-pbvhr-xbdq3-wb3j8 in /home/jenkins/agent/workspace/maven
[Pipeline] {
[Pipeline] container
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run shell)
[Pipeline] sh
+ echo hello world
hello world
[Pipeline] sh
+ mvn -version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/apache-maven-3.8.4
Java version: 1.8.0_292, vendor: AdoptOpenJDK, runtime: /opt/java/openjdk/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-1160.15.2.el7.x86_64", arch: "amd64", family: "unix"
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS
</code></pre> 
<h3><a id="4_PodkubectlDocker_416"></a>4. Pod内使用kubectl、Docker命令</h3> 
<p>如果再springboot 项目中没有用<code>dockerfile-maven-plugin</code> 插件则需要手动使用 docker 命令打包并push到私服</p> 
<h4><a id="41_docker__420"></a>4.1 docker 命令</h4> 
<p>添加两个主机挂载盘</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/1bc4497da11c7b0f1be326c7eedfc812.png" alt="image-20211210153140004"></p> 
<p>或者直接用命令方式</p> 
<pre><code>hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker'),
hostPathVolume(mountPath: '/etc/localtime', hostPath: '/etc/localtime')
</code></pre> 
<h5><a id="pipline_436"></a>修改pipline，运行测试</h5> 
<pre><code>podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
        container('jnlp'){
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
                sh 'docker info'
            }
        }
    }
}
</code></pre> 
<p>这时候会报 权限不足错误，因为 宿主机docker使用 root运行的，pipline pod 使用 jenkins（1000） 运行的</p> 
<pre><code>ERROR: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info": dial unix /var/run/docker.sock: connect: 
</code></pre> 
<p>注意：我K8S集群使用root运行的所以权限很高，你如果使用其他账号运行的K8S集群，会遇到/var/run/docker.sock没有访问权限的问题，因为Docker必须是root权限运行，解决办法是：</p> 
<pre><code># Docker 服务重启要重新执行
chmod 777 /var/run/docker.sock
# 另一种方法是使用用户组
</code></pre> 
<h4><a id="42_PodKubectl_466"></a>4.2 Pod内使用Kubectl命令</h4> 
<p><code>jnlp-maven</code> 的 Pod Templates 添加 <strong>容器</strong> <code>kubectl</code>。其中 <code>andanyoung/kubectl</code>是我自己封装的轻量级<code>kubectl</code>命令库原理很简单，参考<a href="https://hub.docker.com/r/andanyoung/kubectl" rel="nofollow">https://hub.docker.com/r/andanyoung/kubectl</a></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/1664b90cc007da72eaa6854dd11bf3b7.png" alt="image-20211210154233166"></p> 
<p>新增PVC（用于保存kubectl的配置）</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/42da10c91c89ac8ee73aca8b2b39a60e.png" alt="image-20211210154316578"></p> 
<h5><a id="pipline_476"></a>修改pipline，运行测试</h5> 
<blockquote> 
 <p>再 jnlp 上运行 <code>mvn</code>,再 jnlp-kubectl 运行 <code>kubectl</code> 命令</p> 
</blockquote> 
<pre><code>podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
            }
            
        container('jnlp-kubectl'){
            stage('Run kubectl') {
                sh 'kubectl config view'
            }
        }
    
    }
}
</code></pre> 
<h5><a id="_498"></a>运行结果</h5> 
<pre><code>[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Created Pod: kubernetes ops/maven-47-spd2w-p9t8w-94194
[Normal][ops/maven-47-spd2w-p9t8w-94194][Scheduled] Successfully assigned ops/maven-47-spd2w-p9t8w-94194 to a3
[Normal][ops/maven-47-spd2w-p9t8w-94194][Pulled] Container image "andanyoung/kubectl" already present on machine
[Normal][ops/maven-47-spd2w-p9t8w-94194][Created] Created container jnlp-kubectl
[Normal][ops/maven-47-spd2w-p9t8w-94194][Started] Started container jnlp-kubectl
[Normal][ops/maven-47-spd2w-p9t8w-94194][Pulled] Container image "andanyoung/jenkins-slave-maven" already present on machine
[Normal][ops/maven-47-spd2w-p9t8w-94194][Created] Created container jnlp
[Normal][ops/maven-47-spd2w-p9t8w-94194][Started] Started container jnlp
Agent maven-47-spd2w-p9t8w-94194 is provisioned from template maven_47-spd2w-p9t8w
---
apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.ops.svc.cluster.local/job/maven/47/"
    runUrl: "job/maven/47/"
  labels:
    jenkins: "slave"
    jenkins/label-digest: "9d6c53e7f3a6bb33281553a37c902ca1c01f8881"
    jenkins/label: "maven_47-spd2w"
  name: "maven-47-spd2w-p9t8w-94194"
spec:
  containers:
  - image: "andanyoung/kubectl"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp-kubectl"
    resources:
      limits: {}
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/.m2"
      name: "volume-0"
      readOnly: false
    - mountPath: "/var/run/docker.sock"
      name: "volume-1"
      readOnly: false
    - mountPath: "/usr/bin/docker"
      name: "volume-2"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  - env:
    - name: "JENKINS_SECRET"
      value: "********"
    - name: "JENKINS_AGENT_NAME"
      value: "maven-47-spd2w-p9t8w-94194"
    - name: "JENKINS_NAME"
      value: "maven-47-spd2w-p9t8w-94194"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.ops.svc.cluster.local/"
    image: "andanyoung/jenkins-slave-maven"
    imagePullPolicy: "IfNotPresent"
    name: "jnlp"
    resources:
      limits:
        memory: "1Gi"
        cpu: "1"
      requests: {}
    tty: true
    volumeMounts:
    - mountPath: "/home/jenkins/.m2"
      name: "volume-0"
      readOnly: false
    - mountPath: "/var/run/docker.sock"
      name: "volume-1"
      readOnly: false
    - mountPath: "/usr/bin/docker"
      name: "volume-2"
      readOnly: false
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    workingDir: "/home/jenkins/agent"
  hostNetwork: false
  imagePullSecrets:
  - name: ""
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - name: "volume-0"
    persistentVolumeClaim:
      claimName: "pvc-maven-m2"
      readOnly: false
  - hostPath:
      path: "/usr/bin/docker"
    name: "volume-2"
  - hostPath:
      path: "/var/run/docker.sock"
    name: "volume-1"
  - emptyDir:
      medium: ""
    name: "workspace-volume"

Running on maven-47-spd2w-p9t8w-94194 in /home/jenkins/agent/workspace/maven
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run shell)
[Pipeline] sh
+ echo hello world
hello world
[Pipeline] sh
+ mvn -version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/apache-maven-3.8.4
Java version: 1.8.0_292, vendor: AdoptOpenJDK, runtime: /opt/java/openjdk/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-1160.15.2.el7.x86_64", arch: "amd64", family: "unix"
[Pipeline] }
[Pipeline] // stage
[Pipeline] container
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run kubectl)
[Pipeline] sh
+ kubectl config view
apiVersion: v1
clusters: null
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS
</code></pre> 
<p>当然你不能运行<code>kubectl get pod</code> 命令，因为现在的用户为 default 用户。没有权限</p> 
<pre><code>+ kubectl get pod
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:ops:default" cannot list resource "pods" in API group "" in the namespace "ops"
</code></pre> 
<h3><a id="5_Jenkinskubernetes_652"></a>5. 为Jenkins创建kubernetes集群账号和证书</h3> 
<p>参考：<a href="https://andyoung.blog.csdn.net/article/details/121809583" rel="nofollow">k8s 带你一步步 创建用户账号(User Account)</a></p> 
<blockquote> 
 <p>接下来我们需要创建一个 名为 jenkins 的 User Account 账号并赋予相应权限</p> 
</blockquote> 
<p>在Master上执行：</p> 
<pre><code># 进入集群CA证书所在目录
cd /etc/kubernetes/pki

# 执行创建证书命令
(umask 077;openssl genrsa -out jenkins.key 2048)

# O=组织信息，CN=用户名
openssl req -new -key jenkins.key -out jenkins.csr -subj "/O=kubernetes/CN=jenkins"

# 签署证书
openssl  x509 -req -in jenkins.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out jenkins.crt -days 3650

</code></pre> 
<h5><a id="kubectl_675"></a>生成kubectl配置文件</h5> 
<blockquote> 
 <p>生成配置文件位置 /root/jenkins.conf</p> 
 <p>master url --server=https://10.31.90.200:8443</p> 
</blockquote> 
<pre><code># 1. 创建集群配置
kubectl config set-cluster k8s --server=https://10.31.90.200:8443 --certificate-authority=ca.crt --embed-certs=true --kubeconfig=/root/jenkins.conf
Cluster "k8s" set.

# 创建jenkins用户信息
kubectl config set-credentials jenkins --client-certificate=jenkins.crt --client-key=jenkins.key --embed-certs=true --kubeconfig=/root/jenkins.conf

# 创建context配置 设置上下文信息,jenkins用户与集群建立关系
kubectl config set-context jenkins@default-cluster --cluster=default-cluster --user=jenkins --kubeconfig=/root/jenkins.conf 

## 切换context
kubectl config use-context default-context --kubeconfig=/root/jenkins.conf

# 查看结果
kubectl config view
</code></pre> 
<pre><code>apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.122.3:6443
  name: default-cluster
contexts:
- context:
    cluster: default-cluster
    user: jenkins
  name: jenkins@default-cluster
current-context: jenkins@default-cluster
kind: Config
preferences: {}
users:
- name: jenkins
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
</code></pre> 
<p>最后生成 <code>/root/jenkins.conf</code> 文件</p> 
<p>最后将该文件放在 配置的 PVC（pvc-kube-config） 下面，再次运行测试</p> 
<h4><a id="_jenkins__725"></a>为 jenkins 用户添加权限</h4> 
<p>切换刚创建的上下文（切换用户）</p> 
<pre><code># 切换用户
kubectl config use-context jenkins@default-cluster

# 测试
kubectl get pod

# 没有权限
Error from server (Forbidden): pods is forbidden: User "jenkins" cannot list resource "pods" in API group "" in the namespace "default"
</code></pre> 
<p>目前新账号没有分配权限无法使用，创建dev-test命名空间，并创建管理该命名空间下pod资源的角色，然后绑定到jenkins账户：</p> 
<h6><a id="_742"></a>创建角色：</h6> 
<pre><code>kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get","list","watch"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["watch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
</code></pre> 
<h6><a id="_767"></a>绑定账号与角色：</h6> 
<pre><code>apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins-role-bind
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins-role
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: jenkins
</code></pre> 
<p>注意：Role和RoleBinding的命名空间都是dev-test权限才生效，否则是不会生效的，账号jenkins此时拥有对dev-test命名空间pod的管理权限。</p> 
<p>测试权限：</p> 
<pre><code># 在Node节点执行
[root@centos7-k8s-node1 ~]# kubectl apply -f ndsutils.yaml -n dev-test
pod/dnsutils created
[root@centos7-k8s-node1 ~]# kubectl get pod -n dev-test
NAME       READY   STATUS    RESTARTS   AGE
dnsutils   1/1     Running   0          11s
[root@centos7-k8s-node1 ~]# kubectl get pod -n default
Error from server (Forbidden): pods is forbidden: User "jenkins" cannot list resource "pods" in API group "" in the namespace "default"
</code></pre> 
<h5><a id="_799"></a>测试运行（增删改查，自行认证吧）</h5> 
<pre><code>podTemplate (inheritFrom: "jnlp-maven"){
    node(POD_LABEL) {
            stage('Run shell') {
                sh 'echo hello world'
                sh 'mvn -version'
            }
            
        container('jnlp-kubectl'){
            stage('Run kubectl') {
                sh 'kubectl get pod -n dev-test'
            }
        }
    }
}
</code></pre> 
<p>运行结果</p> 
<pre><code>+ kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
jenkins-0                    1/1     Running   0          24h
maven-49-gwdm5-101pq-0spr9   2/2     Running   0          10s
</code></pre> 
<h4><a id="jenkins_827"></a>为jenkins账号赋予集群角色</h4> 
<blockquote> 
 <p>Role 只能拥有当前namespace 的权限，而ClusterRole 没有namespace的限制,没有命名空间的概念，集群角色是在所有命名空间有效。</p> 
</blockquote> 
<p>创建集群角色，此集群角色只有查看Pod的权限：</p> 
<pre><code>apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get","list","watch"]
</code></pre> 
<p>绑定账号和集群角色：</p> 
<pre><code>apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: cluster-reader-jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: jenkins
</code></pre> 
<p>在Node节点上执行：</p> 
<pre><code>[root@centos7-k8s-node1 yaml]# kubectl get pod -A
NAMESPACE              NAME                                         READY   STATUS    RESTARTS   AGE
dev-test               dnsutils                                     1/1     Running   0          7m17s
kube-system            coredns-7ff77c879f-ck49p                     1/1     Running   9          5d3h
kube-system            coredns-7ff77c879f-d2xfc                     1/1     Running   10         5d3h
kube-system            etcd-centos7-k8s-master                      1/1     Running   11         5d3h
kube-system            kube-apiserver-centos7-k8s-master            1/1     Running   6          4d1h
kube-system            kube-controller-manager-centos7-k8s-master   1/1     Running   12         5d3h
kube-system            kube-flannel-ds-amd64-52vcn                  1/1     Running   9          5d3h
kube-system            kube-flannel-ds-amd64-vtw58                  1/1     Running   12         5d3h
kube-system            kube-flannel-ds-amd64-xm8d5                  1/1     Running   10         5d3h
kube-system            kube-proxy-l8875                             1/1     Running   18         5d3h
kube-system            kube-proxy-p5fdr                             1/1     Running   9          5d3h
kube-system            kube-proxy-pdvz2                             1/1     Running   16         5d3h
kube-system            kube-scheduler-centos7-k8s-master            1/1     Running   10         5d3h
kube-system            metrics-server-7f6d95d688-vjsbj              1/1     Running   7          4d1h
kubernetes-dashboard   dashboard-metrics-scraper-6b4884c9d5-8hblg   1/1     Running   7          4d1h
kubernetes-dashboard   kubernetes-dashboard-7b544877d5-tz4xj        1/1     Running   7          4d1h
nginx-ingress          coffee-5f56ff9788-2745d                      1/1     Running   6          4d1h
nginx-ingress          coffee-5f56ff9788-c8jlx                      1/1     Running   6          4d1h
nginx-ingress          nginx-ingress-hjqzc                          1/1     Running   6          4d1h
nginx-ingress          nginx-ingress-jfh6h                          1/1     Running   6          4d1h
nginx-ingress          tea-69c99ff568-cpp2k                         1/1     Running   6          4d1h
nginx-ingress          tea-69c99ff568-rmnr2                         1/1     Running   6          4d1h
</code></pre> 
<p>PS: 用户账号有了（User Account），权限也可以配置了。那k8s 发布pod（重启、更新版本等）不是可以实现了吗？请看下回分析。</p>
                </div>
            </body>
            </html>
            